home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Mac Mania 5
/
MacMania 5.toast
/
/
Internet software
/
NewsWatcher
/
NW Source
/
Source
/
news.c
< prev
next >
Wrap
Text File
|
1997-01-10
|
44KB
|
1,620 lines
/*----------------------------------------------------------------------------
news.c
This module handles all transactions with the NNTP server. It acts as
an interface between the rest of NewsWatcher and the resuable nntp.c
module.
Copyright © 1994-1997, Northwestern University.
----------------------------------------------------------------------------*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "glob.h"
#include "dialog.h"
#include "news.h"
#include "nntp.h"
#include "status.h"
#include "newswatcher.h"
#include "charset.h"
#include "text.h"
#include "strutil.h"
#include "memutil.h"
#include "cache.h"
#include "windutil.h"
#include "resutil.h"
#include "fileutil.h"
#include "ic.h"
#define kPostAuthDlg 136
#define kStartupAuthDlg 137
#define kAuthUsername 5
#define kAuthPassword 7
#define kFileBufLen (10*1024)
typedef struct TTruncateAttachedFileInfo {
long pos; /* current position in text received so far */
Boolean flagReqd; /* true if "flag" line required */
} TTruncateAttachedFileInfo;
typedef struct TCopyArticleToFileInfo {
short fileRefNum; /* file ref num */
Ptr fileBuf; /* pointer to output buffer */
Ptr fileBufAux; /* pointer to auxiliary output buffer */
long fileBufAuxSize; /* number of bytes in aux buffer */
long fileBufPos; /* current position in output buffer */
long fileLinePos; /* position in current line */
char fileLastChar; /* last char */
Boolean fileTruncate; /* true to truncate if attached file */
Boolean fileTruncated; /* true if attached file found, should truncate */
TAttachedFileKind fileKind; /* kind of attached file */
Boolean flagReqd; /* true if attached file must include "begin" flag line */
} TCopyArticleToFileInfo;
static NntpStreamRef gNewsStream = nil; /* reference to news server NNTP stream */
/*----------------------------------------------------------------------------
IsLegalBinHexLine
Check a line to see if it contains legal BinHex encoded text.
Entry: p = pointer to line.
len = length of line.
Exit: function result = true if legal BinHex.
----------------------------------------------------------------------------*/
static Boolean IsLegalBinHexLine (unsigned char *p, long len)
{
unsigned char *pEnd, *pBegin;
unsigned char c;
static Boolean legalBinHexChar[] = {
/* 0x00 */
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
/* 0x10 */
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
/* 0x20 */
false, true, true, true, true, true, true, true,
true, true, true, true, true, true, false, false,
/* 0x30 */
true, true, true, true, true, true, true, false,
true, true, true, false, false, false, false, false,
/* 0x40 */
true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, false,
/* 0x50 */
true, true, true, true, true, true, true, false,
true, true, true, true, false, false, false, false,
/* 0x60 */
true, true, true, true, true, true, true, false,
true, true, true, true, true, true, false, false,
/* 0x70 */
true, true, true, false, false, false, false, false,
false, false, false, false, false, false, false, false,
/* 0x80 */
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false,
};
for (pBegin = p, pEnd = p + len; p < pEnd; p++)
if (!legalBinHexChar[*p]) return false;
c = *pBegin;
for (p = pBegin+1; p < pEnd; p++)
if (*p != c) return true;
return false;
}
/*----------------------------------------------------------------------------
IsLegalUULine
Check a line to see if it contains legal uuencode text.
Entry: p = pointer to line.
len = length of line.
Exit: function result = true if legal uuencode.
----------------------------------------------------------------------------*/
static Boolean IsLegalUULine (unsigned char *p, long len)
{
long n;
unsigned char *pEnd, *pBegin;
unsigned char c;
n = *p - ' ';
if (n < 0 || n > 63) return false;
n = (n+2)/3*4;
if (len > n+3 || len < n-3) return false;
for (pBegin = p, pEnd = p + len, p++; p < pEnd; p++)
if (*p < ' ' || *p > '`') return false;
c = *pBegin;
for (p = pBegin+1; p < pEnd; p++)
if (*p != c) return true;
return false;
}
/*----------------------------------------------------------------------------
CheckForAttachedFile
Check for BinHex or UUEncode text.
Entry: t = pointer to text.
len = length of text.
flagReqd = true if the encoded text must include a begin "flag" line.
skipToNextLine = true to skip to beginning of next line.
Exit: function result = true if BinHex or UUEncode text encountered.
*pos = offset in text of beginning of BinHex or UUEncode text if
function result is true, else offset in text of location where
search should be resumed when more text has arrived from the
server.
The text can have either CR or CRLF line terminators.
This function looks for the flag line (if required), followed by
two adjacent lines of the same length at least 60 characters long,
each of which is either a legal BinHex line or a legal uuencode line.
----------------------------------------------------------------------------*/
static Boolean CheckForAttachedFile (Ptr t, long len, Boolean flagReqd,
Boolean skipToNextLine, long *pos)
{
long lenA, lenB;
unsigned char *p, *pEnd, *q, *r;
unsigned char *flagLine, *lineA, *lineB, *nextLine;
Boolean haveBeginFlagLine, beginFlagLineIsBinHex, foundIt;
p = (unsigned char*)t;
pEnd = p + len - 1;
if (skipToNextLine) {
while (p < pEnd && *p != CR) p++;
while (p < pEnd && (*p == CR || *p == LF)) p++;
if (p >= pEnd) return false;
}
while (p < pEnd) {
if (flagReqd) {
haveBeginFlagLine = false;
flagLine = p;
while (flagLine < pEnd) {
q = flagLine;
while (q < pEnd && *q != CR) q++;
while (q < pEnd && (*q == CR || *q == LF)) q++;
if (q >= pEnd) break;
if (*flagLine == '(') {
if (flagLine + 39 >= pEnd) break;
if (strncmp((char*)flagLine,
"(This file must be converted with BinHex", 39) == 0)
{
haveBeginFlagLine = true;
beginFlagLineIsBinHex = true;
break;
}
} else if (*flagLine == 'b') {
if (flagLine + 9 >= pEnd) break;
if (strncmp((char*)flagLine, "begin ", 6) == 0) {
r = flagLine + 6;
if (isoctal(*r) && isoctal(*(r+1)) && isoctal(*(r+2))) {
haveBeginFlagLine = true;
beginFlagLineIsBinHex = false;
break;
}
}
}
flagLine = q;
}
if (!haveBeginFlagLine) {
*pos = (char*)flagLine - t;
return false;
}
p = flagLine;
lineA = q;
if (lineA >= pEnd) break;
} else {
lineA = p;
}
q = lineA;
while (q < pEnd && *q != CR) q++;
lenA = q - lineA;
while (q < pEnd && (*q == CR || *q == LF)) q++;
lineB = q;
if (lineB >= pEnd) break;
nextLine = flagReqd ? lineA : lineB;
if (lenA < 60) {
p = nextLine;
continue;
}
q = lineB;
while (q < pEnd && *q != CR) q++;
if (q >= pEnd) break;
lenB = q - lineB;
if (lenA != lenB) {
p = nextLine;
continue;
}
if (flagReqd) {
if (beginFlagLineIsBinHex) {
foundIt = IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB);
} else {
foundIt = IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB);
}
} else {
foundIt = (IsLegalBinHexLine(lineA, lenA) && IsLegalBinHexLine(lineB, lenB)) ||
(IsLegalUULine(lineA, lenA) && IsLegalUULine(lineB, lenB));
}
if (foundIt) {
*pos = (char*)lineA - t;
return true;
} else {
p = nextLine;
continue;
}
}
*pos = (char*)p - t;
return false;
}
/*----------------------------------------------------------------------------
TruncateAttachedFile
Check for attached file and truncate article if BinHex or uuencode
text encountered.
Entry: t = pointer to raw text received from server so far.
tLen = length of text received from server so far.
*userDataPtr = pointer to TTruncateAttachedFileInfo struct.
Exit: function result = error code = netTruncatedErr if attached
file discovered.
*truncPos = position at which text should be truncated
if error code = netTruncatedErr.
----------------------------------------------------------------------------*/
static OSErr TruncateAttachedFile (Ptr t, long tLen, Ptr userDataPtr,
long *truncPos)
{
TTruncateAttachedFileInfo *x;
Boolean flagReqd;
long pos;
Boolean foundIt;
x = (TTruncateAttachedFileInfo*)userDataPtr;
flagReqd = x->flagReqd;
pos = x->pos;
if (pos >= tLen) return noErr;
foundIt = CheckForAttachedFile(t + pos, tLen - pos, flagReqd, false, &pos);
pos += x->pos;
if (foundIt) {
*truncPos = pos;
return netTruncatedErr;
} else {
x->pos = pos;
return noErr;
}
}
/*----------------------------------------------------------------------------
NetworkError
Process a network error.
Entry: stream = reference to NNTP stream.
host = address of news server, or nil if default server.
err = error code as returned by function in nntp.c.
Entry: function result = error code.
If the error is a server error, the error is reported to the user,
and the function result is userCanceledErr. Otherwise, the error
code is returned as is, and it is the responsibility of the calling
code to report it.
----------------------------------------------------------------------------*/
static OSErr NetworkError (NntpStreamRef stream, char *host, OSErr err)
{
NetServerErrInfo serverErrInfo;
if (err == nntpServerErr) {
NntpGetServerErrInfo(stream, &serverErrInfo);
err = ServerErrorMessage(kStrNews, serverErrInfo.command, serverErrInfo.response);
if (err != noErr) return err;
return userCanceledErr;
} else {
if (host == nil) {
p2cstr(gPrefs.newsServerName);
SaveNetErrorInfo(kStrNews, (char*)gPrefs.newsServerName);
c2pstr((char*)gPrefs.newsServerName);
} else {
SaveNetErrorInfo(kStrNews, host);
}
return err;
}
}
/*----------------------------------------------------------------------------
SetServerOptions
Set news server options from preferences.
Exit: *options = current server options.
----------------------------------------------------------------------------*/
static void SetServerOptions (NntpStreamOptions *options)
{
MyICReadSharedPrefs(kICNewsAuthUsername);
MyICReadSharedPrefs(kICNewsAuthPassword);
options->idleTime = 10;
options->useXPAT = gPrefs.useXPAT;
options->sendModeReader = !gPrefs.noModeReader;
options->batchedCmds = gPrefs.batchedGroupCmds;
options->newConnection = !gPrefs.noNewConnection;
options->authOnConnect = gPrefs.authAtStartup;
strcpy(options->username, gPrefs.authUsername);
strcpy(options->password, gPrefs.authPassword);
}
/*----------------------------------------------------------------------------
GetAuthInfo
Get authorization information.
Entry: dlgID = dialog id.
statusMsg = status message, C-format, or nil if startup call.
Exit: function result = error code.
gPrefs.authUsername = authorization username.
gPrefs.authPassword = authorization password.
----------------------------------------------------------------------------*/
static OSErr GetAuthInfo (short dlgID, char *statusMsg)
{
DialogPtr dlg = nil;
short item;
CStr255 tempStr;
short len;
char username[32];
char password[32];
OSErr err = noErr;
MyICReadSharedPrefs(kICNewsAuthUsername);
MyICReadSharedPrefs(kICNewsAuthPassword);
if (*gPrefs.authUsername != 0 && *gPrefs.authPassword != 0) return noErr;
err = MyGetNewDialog(dlgID, ok, cancel, &dlg);
if (err != noErr) return err;
RestoreMovableModalDialogPosition(dlg, gPrefs.authLoc);
strcpy(username, gPrefs.authUsername);
DlgSetCString(dlg, kAuthUsername, username);
SetItemUSAsciiNoBlank(dlg, kAuthUsername);
SetItemMaxLength(dlg, kAuthUsername, 31);
strcpy(password, gPrefs.authPassword);
len = strlen(password);
memset(tempStr, '•', len);
tempStr[len] = 0;
DlgSetCString(dlg, kAuthPassword, tempStr);
SetItemPassword(dlg, kAuthPassword, password);
SetItemMaxLength(dlg, kAuthPassword, 31);
if (*username == 0) {
SelectDialogItemText(dlg, kAuthUsername, 0, 0);
} else if (*password == 0) {
SelectDialogItemText(dlg, kAuthPassword, 0, 0);
} else {
SelectDialogItemText(dlg, kAuthUsername, 0, kMaxShort);
}
do {
DlgEnableItem(dlg, ok, *username != 0 && *password != 0);
MyMovableModalDialog(dlg, DialogFilter, &item);
if (item == kAuthUsername) DlgGetCString(dlg, item, username);
} while (item != ok && item != cancel);
if (item == ok) {
strcpy(gPrefs.authUsername, username);
strcpy(gPrefs.authPassword, password);
MyICWriteSharedPrefs(kICNewsAuthUsername);
MyICWriteSharedPrefs(kICNewsAuthPassword);
}
SaveMovableModalDialogPosition(dlg, &gPrefs.authLoc);
err = DoClose(dlg);
if (err != noErr) return err;
if (item == ok && statusMsg != nil) {
err = DisplayStatusMessage(statusMsg);
if (err != noErr) return err;
}
return item == ok ? noErr : userCanceledErr;
}
/*----------------------------------------------------------------------------
TextHasAttachedFile
Check article text to see if it includes an attached file.
Entry: text = pointer to article text.
length = length of text.
Exit: *fileKind = kind of attached file.
----------------------------------------------------------------------------*/
static void TextHasAttachedFile (Ptr text, long length, TAttachedFileKind *fileKind)
{
char *p, *pEnd, *q;
for (p = text, pEnd = p + length; p < pEnd; p++) {
if (*p == CR) {
q = p+1;
if (q >= pEnd) break;
if (*q == '(') {
if (strncmp(q, "(This file must be converted with BinHex", 39) == 0) {
*fileKind = kBinHex;
return;
}
} else if (*q == 'b') {
if (strncmp(q, "begin ", 6) == 0) {
q += 6;
if (q >= pEnd) break;
if (isoctal(*q) && isoctal(*(q+1)) && isoctal(*(q+2))) {
*fileKind = kUUEncode;
return;
}
}
}
}
}
*fileKind = kNoAttachedFile;
}
/*----------------------------------------------------------------------------
FlushFileBuf
Flush the file buffer.
Entry: len = length of buffer.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr FlushFileBuf (long len, TCopyArticleToFileInfo *x)
{
OSErr err = noErr;
long headLen, tailLen, pos;
MapLatin1ToMacPtr(x->fileBuf, len);
if (x->fileTruncate) {
if (x->fileBufAuxSize > 0) {
headLen = len > 200 ? 200 : len;
BlockMoveData(x->fileBuf, x->fileBufAux + x->fileBufAuxSize, headLen);
x->fileBufAuxSize += headLen;
if (CheckForAttachedFile(x->fileBufAux, x->fileBufAuxSize, x->flagReqd, true, &pos)) {
if (pos > 0) {
err = MyFSWriteNoCache(x->fileRefNum, &pos, x->fileBufAux, GiveTime);
if (err != noErr) return err;
}
x->fileTruncated = true;
return noErr;
}
x->fileBufAuxSize -= headLen;
err = MyFSWriteNoCache(x->fileRefNum, &x->fileBufAuxSize, x->fileBufAux, GiveTime);
if (err != noErr) return err;
}
if (CheckForAttachedFile(x->fileBuf, len, x->flagReqd, true, &pos)) {
if (pos > 0) {
err = MyFSWriteNoCache(x->fileRefNum, &pos, x->fileBuf, GiveTime);
if (err != noErr) return err;
}
x->fileTruncated = true;
return noErr;
}
tailLen = len > 200 ? 200 : len;
len -= tailLen;
BlockMoveData(x->fileBuf + len, x->fileBufAux, tailLen);
x->fileBufAuxSize = tailLen;
if (len > 0) {
err = MyFSWriteNoCache(x->fileRefNum, &len, x->fileBuf, GiveTime);
if (err != noErr) return err;
}
} else {
err = MyFSWriteNoCache(x->fileRefNum, &len, x->fileBuf, GiveTime);
if (err != noErr) return err;
if (x->fileKind == kNoAttachedFile) {
if (x->fileBufAuxSize > 0) {
headLen = len > 200 ? 200 : len;
BlockMoveData(x->fileBuf, x->fileBufAux + x->fileBufAuxSize, headLen);
x->fileBufAuxSize += headLen;
TextHasAttachedFile(x->fileBufAux, x->fileBufAuxSize, &x->fileKind);
}
if (x->fileKind == kNoAttachedFile)
TextHasAttachedFile(x->fileBuf, len, &x->fileKind);
if (x->fileKind == kNoAttachedFile) {
tailLen = len > 200 ? 200 : len;
BlockMoveData(x->fileBuf + len - tailLen, x->fileBufAux, tailLen);
x->fileBufAuxSize = tailLen;
}
}
}
return noErr;
}
/*----------------------------------------------------------------------------
CopyArticleToFileChunkFunction
Copy one article chunk to a file.
Entry: t = pointer to chunk.
tLen = length of chunk.
userDataPtr = pointer to TCopyArticleToFileInfo struct.
Exit: function result = error code.
----------------------------------------------------------------------------*/
static OSErr CopyArticleToFileChunkFunction (Ptr t, long tLen,
Ptr userDataPtr, long *unused)
{
register char c, d;
register Ptr p;
Ptr pEnd;
short nSpace;
OSErr err = noErr;
TCopyArticleToFileInfo *x;
x = (TCopyArticleToFileInfo*)userDataPtr;
if (tLen == 0) return noErr;
if (x->fileLastChar != 0) {
c = x->fileLastChar;
} else {
c = *t;
t++;
tLen--;
}
p = x->fileBuf + x->fileBufPos;
pEnd = x->fileBuf + kFileBufLen - 10;
while (tLen > 0) {
if (p >= pEnd) {
err = FlushFileBuf(p - x->fileBuf, x);
if (err != noErr) return err;
if (x->fileTruncated) return netTruncatedErr;
p = x->fileBuf;
}
d = *t;
if (c == BS && d == '_' || c == '_' && d == BS) {
t++;
c = *t;
t++;
tLen -= 2;
} else if (c == CR && d == LF) {
*p++ = CR;
t++;
c = *t;
t++;
tLen -= 2;
x->fileLinePos = 0;
} else if (x->fileLinePos == 0 && c == '.' && d == '.') {
*p++ = '.';
t++;
c = *t;
t++;
tLen -= 2;
x->fileLinePos = 1;
} else if (c == '\t') {
nSpace = 8 - (x->fileLinePos % 8);
while (nSpace--) *p++ = ' ';
x->fileLinePos += nSpace;
c = d;
t++;
tLen--;
} else if (c >= ' ' || c == CR || c == FF || c < 0) {
*p++= c;
x->fileLinePos++;
c = d;
t++;
tLen--;
} else {
c = d;
t++;
tLen--;
}
}
if (tLen == 0) {
x->fileLastChar = c;
} else {
x->fileLastChar = 0;
}
x->fileBufPos = p - x->fileBuf;
return noErr;
}
/*----------------------------------------------------------------------------
BuildXPAT
Build a regular expression string for an XPAT command.
Entry: pattern = substring for which we are searching.
regExpLen = max length of regular expression.
Exit: regExp = regular expression.
----------------------------------------------------------------------------*/
static void BuildXPAT (char *pattern, char *regExp, short regExpLen)
{
unsigned char *p, *q, *qEnd, *e;
unsigned char equivs[256];
short numEquiv;
p = (unsigned char*)pattern;
q = (unsigned char*)regExp;
qEnd = q + regExpLen;
if (q >= qEnd) goto exit;
*q++ = '*';
while (*p != 0) {
GetEquivalentCharacters(*p, equivs, &numEquiv);
if (numEquiv > 1) {
if (q >= qEnd) goto exit;
*q++ = '[';
}
e = equivs;
while (*e != 0) {
if (*e == '*' || *e == '?' || *e == '\\' || *e == '[' || *e == ']') {
if (q >= qEnd) goto exit;
*q++ = '\\';
}
if (q >= qEnd) goto exit;
*q++ = *e++;
}
if (numEquiv > 1) {
if (q >= qEnd) goto exit;
*q++ = ']';
}
p++;
}
if (q >= qEnd) goto exit;
*q++ = '*';
if (q >= qEnd) goto exit;
*q = 0;
MapMacToLatin1Ptr(regExp, (char*)q - regExp);
return;
exit:
*(regExp + regExpLen - 1) = 0;
return;
}
/*----------------------------------------------------------------------------
MatchPattern
Match a pattern.
Entry: pattern = substring for which we are searching, in Mac 8 bit
character set.
string = string to be searched, in Latin-1 8 bit character set.
Exit: function result = true if string contains pattern.
----------------------------------------------------------------------------*/
static Boolean MatchPattern (char *pattern, char *string)
{
CStr255 str;
MapLatin1ToMacStr(string, str);
return MyIsASubstring(str, pattern);
}
/*----------------------------------------------------------------------------
StartNNTP
Open a connection to the news server.
Exit: function result = error code.
----------------------------------------------------------------------------*/
OSErr StartNNTP (void)
{
NntpStreamOptions options;
OSErr err = noErr;
gCancel = false;
MyICReadSharedPrefs(kICNNTPHost);
while (true) {
gNewsStream = nil;
if (gPrefs.authAtStartup) {
err = GetAuthInfo(kStartupAuthDlg, nil);
if (err != noErr) return err;
}
err = DisplayStatusMessageNumber(kStrOpeningConnectionStatusMsg);
if (err != noErr) return err;
SetServerOptions(&options);
p2cstr(gPrefs.newsServerName);
err = NntpOpen((char*)gPrefs.newsServerName, &options, &gNewsStream);
c2pstr((char*)gPrefs.newsServerName);
if (gPrefs.authAtStartup && err == nntpAuthFailedErr) {
*gPrefs.authPassword = 0;
MyICWriteSharedPrefs(kICNewsAuthPassword);
gNewsStream = nil;
ErrorMessageNumber(kStrAuthFailed);
} else if (err != noErr) {
goto exit;
} else {
break;
}
}
return noErr;
exit:
err = NetworkError(gNewsStream, nil, err);
if (gNewsStream != nil) NntpClose(gNewsStream);
gNewsStream = nil;
return err;
}
/*----------------------------------------------------------------------------
EndNNTP
Close the connection to the NNTP server.
----------------------------------------------------------------------------*/
void EndNNTP (void)
{
if (gNewsStream != nil) NntpClose(gNewsStream);
gNewsStream = nil;
}
/*----------------------------------------------------------------------------
GetGroupNames
Get a list of group names from the server.
Entry: lastTime = 0: Fetch the entire full group list.
lastTime != 0: Fetch just the groups which have been created
since lastTime - 36 hours.
Exit: function result = error code.
*strings = handle to C-format group name strings.
*numGroups = number of group names.
The group names are mapped from Latin-1 to the Mac character set.
The group names are truncated to 127 characters.
Groups with status 'x' and '=' are filtered out (not returned).
----------------------------------------------------------------------------*/
OSErr GetGroupNames (unsigned long lastTime, Handle *strings, long *numGroups)
{
OSErr err;
Handle theStrings;
long theNumGroups;
if (lastTime != 0) {
/* Just get groups added since "lastTime". Subtract 36 hours to compensate
for clock drift, daylight savings time, and server and client in
different time zones. */
lastTime -= 60L*60L*36L;
}
err = NntpGetGroupNames(gNewsStream, lastTime, "x=",
&theStrings, &theNumGroups);
if (err != noErr) return NetworkError(gNewsStream, nil, err);
MapLatin1ToMacHandle(theStrings);
#ifdef kDevelopmentVersion
#define kFactor 5
if (lastTime == 0) {
long len;
char *q, *p;
long i, j;
EventRecord ev;
EventAvail(everyEvent, &ev);
if ((ev.modifiers & optionKey) != 0) {
len = MyGetHandleSize(theStrings);
err = MySetHandleSize(theStrings, (kFactor+1)*len);
if (err != noErr) {
MyDisposeHandle(theStrings);
return err;
}
MyHLock(theStrings);
p = *theStrings;
q = *theStrings + kFactor*len;
BlockMoveData(p, q, len);
for (i = 0; i < theNumGroups; i++) {
len = strlen(q);
for (j = 0; j < kFactor; j++) {
BlockMoveData(q, p, len);
p += len;
if (j > 0) *p++ = '0' + j;
*p++ = 0;
}
q += len+1;
}
len = p - *theStrings;
MyHUnlock(theStrings);
MySetHandleSize(theStrings, len);
theNumGroups = kFactor*theNumGroups;
}
}
#endif
*strings = theStrings;
*numGroups = theNumGroups;
return noErr;
}
/*----------------------------------------------------------------------------
GetArticle
Get one article from an NNTP server.
Entry: host = address of news server, or nil to use default server.
port = port number on news server if host not nil.
groupName = name of group, or nil if fetching by message id.
number = article number. Ignored if fetching by message id.
id = message id string, including < and > delimiters. Ignored
if fetching by article number.
part = which part of the article to get:
"ARTICLE": full article text, header and body.
"HEAD": only article header.
"BODY": only article body.
truncateIfAttachedFile = true to truncate the article if it
contains an attached file. The BinHex or uuencode text
for the attached file is not returned.
requireEncodedTextBeginLIne = true if BinHex or uuencode text
must include special "begin" flag line.
Exit: function result = error code.
*text = handle to article text, or nil if article does not
exit on server or group does not exist.
*textLength = length of article text.
*attachedFile = true if article contains an attached file which
was truncated.
Backspace-underscore and underscore-backspace sequences are filtered out
of the article, as are all non-printable characters except for TAB and CR and FF.
Tabs are expanded to 8 column tab stops.
Trailing blank lines are deleted.
CRLF line terminators are mapped to CR.
Leading double-period characters on lines are mapped to single periods.
8 bit characters are mapped from Latin-1.
----------------------------------------------------------------------------*/
OSErr GetArticle (char *host, short port,
char *groupName, long number, char *id, char *part,
Boolean truncateIfAttachedFile, Boolean flagReqd,
Handle *text, long *textLength, Boolean *attachedFile)
{
long len, newLen, col, nSpace;
unsigned char **txt = nil;
unsigned char *p, *pEnd, *q, *r;
OSErr err = noErr;
NntpStreamRef stream = nil;
CStr255 hostAndPortString;
TTruncateAttachedFileInfo x;
if (host == nil) {
stream = gNewsStream;
} else {
sprintf(hostAndPortString, "%s,%d", host, port);
err = NntpOpen(hostAndPortString, nil, &stream);
if (err != noErr) goto exit;
}
if (truncateIfAttachedFile) {
x.pos = 0;
x.flagReqd = flagReqd;
err = NntpGetArticle(stream, groupName, number, id, part,
(Handle*)&txt, TruncateAttachedFile, (Ptr)&x);
if (err == netTruncatedErr) {
if (attachedFile != nil) *attachedFile = true;
} else {
if (attachedFile != nil) *attachedFile = false;
if (err != noErr) goto exit;
}
} else {
err = NntpGetArticle(stream, groupName, number, id, part,
(Handle*)&txt, nil, nil);
if (err != noErr) goto exit;
}
if (host != nil) {
NntpClose(stream);
stream = nil;
}
len = MyGetHandleSize(txt);
for (p = *txt, pEnd = p + len, q = *txt; p < pEnd;) {
if (p < pEnd-1 && ((*p == BS && *(p+1) == '_') || (*p == '_' && *(p+1) == BS))) {
/* Filter underscore backspace and backspace underscore */
p += 2;
} else if (*p >= ' ' || *p == CR || *p == '\t' || *p == FF) {
/* Copy printable character as is */
*q++ = *p++;
} else {
/* Filter unprintable character */
p++;
}
}
/* Trim trailing blank lines */
q--;
while (q >= *txt && *q == CR) q--;
q++;
len = q - *txt;
MySetHandleSize(txt, len);
/* Map Latin1 */
MapLatin1ToMacHandle((Handle)txt);
/* Expand tabs. */
newLen = len;
r = *txt;
for (p = *txt, pEnd = p + len; p < pEnd; p++) {
if (*p == '\t') {
col = p - r;
nSpace = 8 - (col % 8);
newLen += nSpace - 1;
r = p+1;
} else if (*p == CR) {
r = p+1;
}
}
if (newLen > len) {
err = MySetHandleSize(txt, newLen);
if (err != noErr) goto exit;
BlockMoveData(*txt, *txt + newLen - len, len);
r = *txt + newLen - len;;
for (p = r, pEnd = p + len, q = *txt; p < pEnd; p++) {
if (*p == '\t') {
col = p - r;
nSpace = 8 - (col % 8);
while (nSpace--) *q++ = ' ';
r = p+1;
} else if (*p == CR) {
*q++ = *p;
r = p+1;
} else {
*q++ = *p;
}
}
len = newLen;
}
*text = (Handle)txt;
*textLength = len;
return noErr;
exit:
if (err == nntpNoSuchGroupErr || err == nntpNoSuchArticleErr) {
*text = nil;
return noErr;
} else {
err = NetworkError(stream, host, err);
}
if (host != nil && stream != nil) NntpClose(stream);
MyDisposeHandle(txt);
return err;
}
/*----------------------------------------------------------------------------
CopyArticleToFile
Get one article from the NNTP server and copy it to a file.
Entry: groupName = name of group, or nil if fetching by message id.
number = article number. Ignored if fetching by message id.
id = message id string, including < and > delimiters. Ignored
if fetching by article number.
part = which part of the article to get:
"ARTICLE": full article text, header and body.
"HEAD": only article header.
"BODY": only article body.
refNum = reference number of open file.
truncateIfAttachedFile = true to truncate the article if it
contains an attached file. The BinHex or uuencode text
for the attached file is not copied to the file.
flagReqd = true if BinHex or uuencode text
must include special "begin" flag line.
Exit: function result = error code.
*fileKind = attached file kind, if !truncateIfAttachedFile.
Backspace-underscore and underscore-backspace sequences are filtered out
of the article, as are all non-printable characters except for TAB and CR and FF.
Tabs are expanded to 8 column tab stops.
CRLF line terminators are mapped to CR.
Leading double-period characters on lines are mapped to single periods.
8 bit characters are mapped from Latin-1.
----------------------------------------------------------------------------*/
OSErr CopyArticleToFile (char *groupName, long number, char *id, char *part,
short refNum, Boolean truncateIfAttachedFile,
Boolean flagReqd, TAttachedFileKind *fileKind)
{
OSErr err = noErr;
TCopyArticleToFileInfo x;
x.fileBuf = x.fileBufAux = nil;
x.fileRefNum = refNum;
err = MyNewPtr(kFileBufLen, &x.fileBuf);
if (err != noErr) goto exit;
err = MyNewPtr(400, &x.fileBufAux);
if (err != noErr) goto exit;
x.fileBufAuxSize = 0;
x.fileBufPos = 0;
x.fileLinePos = 0;
x.fileLastChar = 0;
x.fileTruncate = truncateIfAttachedFile;
x.fileTruncated = false;
x.fileKind = kNoAttachedFile;
x.flagReqd = flagReqd;
err = NntpGetArticle(gNewsStream, groupName, number, id, part,
nil, CopyArticleToFileChunkFunction, (Ptr)&x);
if (err == netTruncatedErr) err = noErr;
if (err != noErr) goto exit;
if (!x.fileTruncated) {
if (x.fileLastChar != 0) {
*(x.fileBuf + x.fileBufPos) = x.fileLastChar;
x.fileBufPos++;
}
if (x.fileBufPos > 0) err = FlushFileBuf(x.fileBufPos, &x);
if (x.fileTruncate && x.fileBufAuxSize > 0) {
err = MyFSWriteNoCache(x.fileRefNum, &x.fileBufAuxSize, x.fileBufAux, GiveTime);
if (err != noErr) goto exit;
}
}
*fileKind = x.fileKind;
exit:
MyDisposePtr(x.fileBuf);
MyDisposePtr(x.fileBufAux);
x.fileBuf = nil;
x.fileBufAux = nil;
if (err == nntpNoSuchGroupErr || err == nntpNoSuchArticleErr) {
*fileKind = kArtNotOnServer;
return noErr;
} else {
return NetworkError(gNewsStream, nil, err);
}
}
/*----------------------------------------------------------------------------
PostArticle
Post an article.
Entry: text = handle to article text, including header.
statusMsg = status message, C-format.
Exit: function result = error code.
postIndeterminate = true if entire article sent, but error occured
or user canceled before final server response received. The
article may or may not have been posted successfully.
----------------------------------------------------------------------------*/
OSErr PostArticle (Handle text, char *statusMsg, Boolean *postIndeterminate)
{
OSErr err = noErr;
NetServerErrInfo serverErrInfo;
Boolean munged;
err = NntpPostArticle(gNewsStream, text, true, postIndeterminate, &munged);
if (err == nntpServerErr) {
NntpGetServerErrInfo(gNewsStream, &serverErrInfo);
if (serverErrInfo.responseCode != 480) goto exit;
while (true) {
err = GetAuthInfo(kPostAuthDlg, statusMsg);
if (err != noErr) return err;
ResetNewsServerOptions();
err = NntpAuthorize(gNewsStream);
if (err == nntpAuthFailedErr) {
*gPrefs.authPassword = 0;
MyICWriteSharedPrefs(kICNewsAuthPassword);
ErrorMessageNumber(kStrAuthFailed);
} else if (err != noErr) {
goto exit;
} else {
break;
}
}
err = NntpPostArticle(gNewsStream, text, !munged, postIndeterminate,
&munged);
if (err != noErr) goto exit;
} else if (err != noErr) {
goto exit;
}
return noErr;
exit:
return NetworkError(gNewsStream, nil, err);
}
/*----------------------------------------------------------------------------
GetGroupArticleRange
Query the NNTP server to get the current article range for a single group.
Entry: theGroup = pointer to group record.
Exit: function result = error code.
*groupExits = true if group exists.
theGroup->firstMess = first article in range.
theGroup->lastMess = last article in range.
theGroup->numUnread = number of articles in range.
----------------------------------------------------------------------------*/
OSErr GetGroupArticleRange (TGroup *theGroup, Boolean *groupExists)
{
CStr255 groupName;
long first, last, count;
OSErr err = noErr;
*groupExists = true;
strcpy(groupName, *gGroupNames + theGroup->nameOffset);
err = NntpGetGroupInfo(gNewsStream, groupName, &first, &last, &count);
if (err != noErr) goto exit;
if (first == 0 && last == 0) {
/* Special case empty group: set firstMess = lastMess + 1. */
theGroup->firstMess = theGroup->lastMess + 1;
err = AgeArticleCache(groupName, kMaxLong);
if (err != noErr) return err;
} else {
if (first <= 0) first = 1;
if (last < first) last = first - 1;
theGroup->firstMess = first;
theGroup->lastMess = last;
err = AgeArticleCache(groupName, first);
if (err != noErr) return err;
}
theGroup->numUnread = theGroup->lastMess - theGroup->firstMess + 1;
return noErr;
exit:
if (err == nntpNoSuchGroupErr) {
*groupExists = false;
return noErr;
} else {
return NetworkError(gNewsStream, nil, err);
}
}
/*----------------------------------------------------------------------------
GetGroupArrayArticleRanges
Query the NNTP server to get current article range info for designated
groups in a group list array.
Entry: groupArray = handle to group array.
numGroups = number of groups in group array.
Each group in the array to be updated is marked with status='x'.
Exit: function result = error code.
Deleted groups are marked with status='d'.
----------------------------------------------------------------------------*/
OSErr GetGroupArrayArticleRanges (TGroup **groupArray, long numGroups)
{
NntpGroupInfoHandle info = nil;
NntpGroupInfoPtr q, qEnd;
TGroup *p, *pEnd;
long first, last;
long numGroupsToUpdate = 0;
CStr255 groupName;
OSErr err = noErr;
for (p = *groupArray, pEnd = p + numGroups; p < pEnd; p++)
if (p->status == 'x') numGroupsToUpdate++;
err = MyNewHandle(numGroupsToUpdate * sizeof(NntpGroupInfo), &info);
if (err != noErr) goto exit;
for (p = *groupArray, pEnd = p + numGroups, q = *info; p < pEnd; p++) {
if (p->status == 'x') {
q->offset = p->nameOffset;
q++;
}
}
err = NntpGetMultipleGroupInfo(gNewsStream, info, numGroupsToUpdate, gGroupNames);
if (err != noErr) goto exit;
MyHLock(info);
for (q = *info, qEnd = q + numGroupsToUpdate, p = *groupArray; q < qEnd; q++) {
while (p->status != 'x') p++;
if (q->ok) {
first = q->first;
last = q->last;
strcpy(groupName, *gGroupNames + q->offset);
if (first == 0 && last == 0) {
/* Special case empty group: set firstMess = lastMess + 1. */
p->firstMess = p->lastMess + 1;
err = AgeArticleCache(groupName, kMaxLong);
if (err != noErr) goto exit;
} else {
if (first <= 0) first = 1;
if (last < first) last = first - 1;
p->firstMess = first;
p->lastMess = last;
err = AgeArticleCache(groupName, first);
if (err != noErr) goto exit;
}
p->numUnread = p->lastMess - p->firstMess + 1;
} else {
p->status = 'd';
}
p++;
}
MyDisposeHandle(info);
return noErr;
exit:
MyDisposeHandle(info);
return NetworkError(gNewsStream, nil, err);
}
/*----------------------------------------------------------------------------
GetHeaders
Get header lines from the news server.
Entry: groupName = the group name.
headerName = the header name.
first = first article number.
last = last article number.
Exit: function result = error code.
*headers = handle to array of THeader records, or nil if
group does not exist.
*strings = handle to header strings.
*numHeaders = number of headers.
----------------------------------------------------------------------------*/
OSErr GetHeaders (char *groupName, char *headerName, long first, long last,
THeader ***headers, Handle *strings, long *numHeaders)
{
OSErr err;
NntpHeaderInfoHandle info;
Handle str;
long num;
unsigned char *p, *pEnd, *q;
err = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
nil, nil, nil, &info, &str, &num);
if (err != noErr) goto exit;
/* Filter unprintable characters. */
p = (unsigned char*)*str;
pEnd = p + MyGetHandleSize(str);
while (p < pEnd) {
q = p;
while (*p != 0) {
if (*p >= ' ') {
*q++ = *p++;
} else {
p++;
}
}
*q = 0;
p++;
}
MapLatin1ToMacHandle(str);
*headers = (THeader**)info;
*strings = str;
*numHeaders = num;
return noErr;
exit:
if (err == nntpNoSuchGroupErr) {
*headers = nil;
return noErr;
} else {
return NetworkError(gNewsStream, nil, err);
}
}
/*----------------------------------------------------------------------------
SearchHeaders
Search header lines and returns matches. The search is for any
substring and is case-insensitive. The XPAT command is used if the
server supports it and the "use XPAT" preference is turned on.
Otherwise, we do it the hard way.
Entry: groupName = the group name.
headerName = the header name.
first = first article number.
last = last article number.
pattern = search string. WARNING: This string is modified
by the function.
Exit: function result = error code.
*headers = handle to array of THeader records, or nil if
group does not exist.
*numHeaders = number of headers.
----------------------------------------------------------------------------*/
OSErr SearchHeaders (char *groupName, char *headerName, long first, long last,
char *pattern, THeader ***headers, short *numHeaders)
{
OSErr err;
NntpHeaderInfoHandle info;
Handle str = nil;
long num;
err = NntpGetHeaders(gNewsStream, groupName, first, last, headerName,
pattern, BuildXPAT, MatchPattern, &info, &str, &num);
if (err != noErr) goto exit;
MyDisposeHandle(str);
*headers = (THeader**)info;
*numHeaders = num;
return noErr;
exit:
MyDisposeHandle(str);
if (err == nntpNoSuchGroupErr) {
*headers = nil;
return noErr;
} else {
return NetworkError(gNewsStream, nil, err);
}
}
/*----------------------------------------------------------------------------
DoGetServerInfo
Handle the "Get Server Info" command.
Exit: function result = error code.
----------------------------------------------------------------------------*/
OSErr DoGetServerInfo (void)
{
char str[1000];
char *p, *pEnd, *q;
Handle serverHelpResponse = nil;
Handle serverInfoText = nil;
Str255 dateStr, timeStr, versStr, title;
CStr255 addrStr;
unsigned long rawSecs;
unsigned long ipAddr;
CStr255 helloMsg;
CStr255 fmt;
OSErr err = noErr;
WindowPtr wind;
NetServerErrInfo serverErrInfo;
MyICReadSharedPrefs(kICNNTPHost);
GetPString(kStrServerInfoWindTitle, title);
if (CheckTextWindowAlreadyOpen(title)) return noErr;
HiliteMenu(0);
GetDateTime(&rawSecs);
IUDateString(rawSecs, abbrevDate, dateStr);
IUTimeString(rawSecs, false, timeStr);
err = GetVersionString(versStr);
if (err != noErr) goto exit;
NntpGetIPAddr(gNewsStream, &ipAddr);
NntpGetHello(gNewsStream, helloMsg);
MapLatin1ToMacStr(helloMsg, helloMsg);
sprintf(addrStr, "%ld.%ld.%ld.%ld", (ipAddr >> 24) & 0xff, (ipAddr >> 16) & 0xff,
(ipAddr >> 8) & 0xff, ipAddr & 0xff);
err = NntpGetHelp(gNewsStream, &serverHelpResponse);
if (err != noErr) goto exit;
NntpGetServerErrInfo(gNewsStream, &serverErrInfo);
MapLatin1ToMacStr(serverErrInfo.response, serverErrInfo.response);
MapLatin1ToMacHandle(serverHelpResponse);
GetCString(kStrServerInfoFmt, fmt);
p2cstr(dateStr);
p2cstr(timeStr);
p2cstr(versStr);
p2cstr(gPrefs.newsServerName);
sprintf(str, fmt, dateStr, timeStr, versStr, gPrefs.newsServerName,
addrStr, helloMsg, serverErrInfo.response);
c2pstr((char*)gPrefs.newsServerName);
p = *serverHelpResponse + MyGetHandleSize(serverHelpResponse) - 1;
while (p > *serverHelpResponse && *p == CR) p--;
MySetHandleSize(serverHelpResponse, p - *serverHelpResponse + 1);
for (p = *serverHelpResponse, pEnd = p + MyGetHandleSize(serverHelpResponse);
p < pEnd;
p++)
{
if (*p == '<') {
q = p+1;
while (q < pEnd && *q != '>' && *q != '@' && *q != CR) q++;
if (q < pEnd && *q == '@') {
q++;
while (q < pEnd && *q != '>' && *q != CR) q++;
if (q < pEnd && *q == '>') {
*p = ' ';
*q = ' ';
break;
}
}
}
}
err = MyPtrToHand(str, &serverInfoText, strlen(str));
if (err != noErr) goto exit;
err = MyHandAndHand(serverHelpResponse, serverInfoText);
if (err != noErr) goto exit;
MyDisposeHandle(serverHelpResponse);
serverHelpResponse = nil;
err = MakeNewTextWindow(title, 0, nil, serverInfoText, &wind);
if (err != noErr) goto exit;
MyDisposeHandle(serverInfoText);
return noErr;
exit:
MyDisposeHandle(serverHelpResponse);
MyDisposeHandle(serverInfoText);
return NetworkError(gNewsStream, nil, err);
}
/*----------------------------------------------------------------------------
ResetNewsServerOptions
Reset the server options on the news server stream. This function must
be called whenever the options change (e.g., when the user changes them
in the prefs dialog).
----------------------------------------------------------------------------*/
void ResetNewsServerOptions (void)
{
NntpStreamOptions options;
SetServerOptions(&options);
if (gNewsStream != nil) NntpSetStreamOptions(gNewsStream, &options);
}
/*----------------------------------------------------------------------------
Reauthenticate
Reauthenticate the user after a change in username or password.
Exit: function result = error code.
----------------------------------------------------------------------------*/
OSErr ReAuthenticate (void)
{
NntpStreamRef oldStream;
OSErr err = noErr;
if (gNewsStream == nil) return noErr;
if (!gPrefs.authAtStartup) {
NntpAbort(gNewsStream);
} else {
oldStream = gNewsStream;
gNewsStream = nil;
err = StartNNTP();
if (err == noErr) {
NntpClose(oldStream);
} else {
gNewsStream = oldStream;
}
}
return err;
}